///\todo upgrade

//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_IMAGE_CCOLOR_H_
#define _BUOLA_IMAGE_CCOLOR_H_

#include <buola/image.h>
#include <buola/utility/uvalue.h>
#include <buola/traits/typename.h>

#include <unordered_map>

namespace buola {
    
DECLARE_TYPENAME_TRAITS(img::CColor,"CColor");

/*namespace buola*/ }

namespace buola { namespace img {

///! \addtogroup image
///@{

DECLARE_NAME(WaveLength)

////////////////////////////////////////////////////////////////////////////

/// color

///
///
///
////////////////////////////////////////////////////////////////////////////

class CColor
{
public:
//! \name constructors
//@{
    //! constructs an invalid color
    CColor()
        :   r(-1.0)
    {}

    //! copy constructor
    CColor(const CColor &pSrc)=default;

    //! from RGBA (or RGB) 32 bit representation
    explicit CColor(uint32_t pColor)
    {
        r=((pColor>>16)&0xff)/255.0;
        g=((pColor>>8)&0xff)/255.0;
        b=(pColor&0xff)/255.0;
        a=(pColor&0xff000000)?(((pColor>>24)&0xff)/255.0):1.0;
    }

    ///from wavelength
    CColor(NWaveLength,double pWL,double pGamma=1.0)
    {
        r=g=b=0.0;
        a=1.0;
        
        if(pWL<380.0||pWL>780.0) return;

        if(pWL<420.0)
        {
            b=0.3+0.7*(pWL-380.0)/(420.0-380.0);
            r=b*-(pWL-440.0)/(440.0-380.0);
        }
        else if(pWL<=440.0)
        {
            r=-(pWL-440.0)/(440.0-380.0);
            b=1.0;
        }
        else if(pWL<=490.0)
        {
            g=(pWL-440.0)/(490.0-440.0);
            b=1.0;
        }
        else if(pWL<=510.0)
        {
            g=1.0;
            b=-1.0*(pWL-510.0)/(510.0-490.0);
        }
        else if(pWL<=580.0)
        {
            r=(pWL-510.0)/(580.0-510.0);
            g=1.0;
        }
        else if(pWL<=645.0)
        {
            r=1.0;
            g=-1.0*(pWL-645.0)/(645.0-580.0);
        }
        else if(pWL<=700.0)
        {
            r = 1.0;
        }
        else
        {
            r=0.3 + 0.7 * (780.0 - pWL) / (780.0 - 700.0);
        }

        if(pGamma!=1.0)
        {
            r=pow(r,pGamma);
            g=pow(g,pGamma);
            b=pow(b,pGamma);
        }
    }
    
    //! from named color
    explicit CColor(const std::wstring &pString,double pAlpha=1.0);

    //! from r,g,b,[a] floating-point component values
    CColor(double pR,double pG,double pB,double pA=1.0)
        :   r(pR)
        ,   g(pG)
        ,   b(pB)
        ,   a(pA)
    {}

    //! from a UValue object
    CColor(const UValue &pValue)
    {
        if(pValue.HasType<uint32_t>())
        {
            *this=CColor(pValue.Get<uint32_t>());
        }
        else if(pValue.HasType<int>())
        {
            *this=CColor(pValue.Get<int>());
        }
        else if(pValue.HasType<std::wstring>())
        {
            *this=CColor(pValue.Get<std::wstring>());
        }
        else if(pValue.HasType<std::string>())
        {
            *this=CColor(utf32(pValue.Get<std::string>()));
        }
        else if(pValue.HasType<CColor>())
        {
            *this=pValue.Get<CColor>();
        }
        else
        {
            throw XArgument("wrong value type in CColor::CColor");
        }
    }
//@}

//! \name setting and getting
//@{
    //! set to H,S,V floating point components
    void SetHSV(double h,double s,double v);

    //! set to H,S,V,A floating point components
    void SetHSVA(double h,double s,double v,double pa)
    {
        SetHSV(h,s,v);
        a=pa;
    }

    //! get 32 bit RGBA representation
    uint32_t RGBA() const
    {
        return (uint32_t(a*255.0)<<24)|(uint32_t(r*255.0)<<16)|(uint32_t(g*255.0)<<8)|uint32_t(b*255.0);
    }

    //! get HSV component
    void GetHSV(double &h,double &s,double &v) const;

    //! true if it represents a valid color
    bool Valid() const
    {
        return (r!=-1.0);
    }

    //! true if both objects represent the same color
    bool operator==(const CColor &pColor) const
    {
        return (r==pColor.r&&g==pColor.g&&b==pColor.b&&a==pColor.a);
    }

    //! true if both objects represent different colors
    bool operator!=(const CColor &pColor) const
    {
        return !operator==(pColor);
    }
//@{

//! \name color operations
//@{
    //! multiplies each channel by the alpha channel
    void Premultiply()
    {
        r*=a;
        g*=a;
        b*=a;
    }

    //! mixes the two colors
    void Mix(const CColor &pRH)
    {
        r=r+pRH.r/2;
        g=g+pRH.g/2;
        b=b+pRH.b/2;
    }

    //! returns a color with this color intensity and the other color tone
    void Tint(const CColor &pColor)
    {
        double lIntensity=(r+g+b)/3;

        r=pColor.r*lIntensity;
        g=pColor.g*lIntensity;
        b=pColor.b*lIntensity;
    }

    //! makes grayscale
    void ToBW()
    {
        double lIntensity=(r+g+b)/3;

        r=lIntensity;
        g=lIntensity;
        b=lIntensity;
    }

    //! gets a darker color
    CColor GetDarker(int pQ) const;
    //! gets a lighter color
    CColor GetLighter(int pQ) const;

    //! static initialization
    static void InitStatic();

    //! get hash table of named colors
    static const std::unordered_map<std::wstring,CColor> &NamedColors() {	return mNamedColors;	}

public:
    double r,g,b,a;

private:    
    //! hash table of named colors
    static std::unordered_map<std::wstring,CColor> mNamedColors;
};

//! represents a color difference
class CColorDiff
{
public:
    double r,g,b,a;
};

inline CColorDiff operator-(const CColor &p1,const CColor &p2)
{
    CColorDiff lResult;
    lResult.r=p1.r-p2.r;
    lResult.g=p1.g-p2.g;
    lResult.b=p1.b-p2.b;
    lResult.a=p1.a-p2.a;
    return lResult;
}

inline CColorDiff operator*(const CColorDiff& pD,double pM)
{
    CColorDiff lResult;
    lResult.r=pD.r*pM;
    lResult.g=pD.g*pM;
    lResult.b=pD.b*pM;
    lResult.a=pD.a*pM;

    return lResult;
}

inline CColorDiff operator*(double pM,const CColorDiff& pD)
{
    CColorDiff lResult;
    lResult.r=pD.r*pM;
    lResult.g=pD.g*pM;
    lResult.b=pD.b*pM;
    lResult.a=pD.a*pM;

    return lResult;
}

inline CColor operator*(const CColor &pC,double pM)
{
    return CColor(pC.r*pM,pC.g*pM,pC.b*pM,pC.a*pM);
}

inline CColor operator/(const CColor &pC,double pM)
{
    return CColor(pC.r/pM,pC.g/pM,pC.b/pM,pC.a/pM);
}

inline CColor operator+(const CColor &pC,const CColorDiff &pD)
{
    return CColor(pC.r+pD.r,pC.g+pD.g,pC.b+pD.b,pC.a+pD.a);
}

inline CColor operator+(const CColor &pC,const CColor &pRH)
{
    return CColor(pC.r+pRH.r,pC.g+pRH.g,pC.b+pRH.b,pC.a+pRH.a);
}

inline CColor operator+(const CColorDiff &pD,const CColor &pC)
{
    return CColor(pC.r+pD.r,pC.g+pD.g,pC.b+pD.b,pC.a+pD.a);
}

inline CColor operator-(const CColor &pC,const CColorDiff &pD)
{
    return CColor(pC.r-pD.r,pC.g-pD.g,pC.b-pD.b,pC.a-pD.a);
}

inline CColor rgb8(uint8_t r,uint8_t g,uint8_t b)
{
    return CColor(r/255.0,g/255.0,b/255.0);
}

inline CColor rgba8(uint8_t r,uint8_t g,uint8_t b,uint8_t a)
{
    return CColor(r/255.0,g/255.0,b/255.0,a/255.0);
}

inline CColor mix(const CColor &p1,const CColor &p2,double pA)
{
    double lB=1.0-pA;
    return CColor(p1.r*lB+p2.r*pA,p1.g*lB+p2.g*pA,p1.b*lB+p2.b*pA,p1.a*lB+p2.a*pA);
}

inline io::CTextWriter &operator<<(io::CTextWriter &pWriter,const CColor &pColor)
{
    return pWriter << "(R:" << pColor.r << " G:" << pColor.g << " B:" << pColor.b << " A:" << pColor.a << ")";
}

const std::vector<CColor> &nice_colors();
const CColor &nice_color(int i);

///@}

/*namespace img*/ } /*namespace buola*/ }

namespace std {
    template<>
    struct hash<buola::img::CColor> : public std::unary_function<buola::img::CColor,std::size_t>
    {
        std::size_t operator()(const buola::img::CColor &pColor) const
        {
            return hash<unsigned int>()(pColor.RGBA());
        }
    };
}

#endif
